#include <ncurses.h>
#include <stdlib.h>
#include <linux/perf_event.h>
#include <fstream>
#include "view.h"
#include "config.h"

using namespace std;

#define LX_LENGTH 2
#define GREEN 1
#define RED 2

struct view_block {
	unsigned line;
	unsigned col;

	unsigned l1x,l2x,l3x,ly;
	unsigned ly_start,ly_end;
	unsigned txtStart,txtEnd;
	char *cpu;
};

static struct view_block blocks[CPU_NUMS] = {0};
static bool verbose = false;
static ofstream log;

enum class MicroArch
{
	L1,
	L2,
	L3
};

struct ViewData
{
	unsigned long hit;
	unsigned long miss;
};

int start_view()
{
	initscr();
	start_color();
	init_pair(GREEN, COLOR_GREEN, COLOR_GREEN);
	init_pair(RED, COLOR_RED, COLOR_RED);

	cbreak();
	noecho();
	nodelay(stdscr, TRUE);

	unsigned rows = (CPU_NUMS - (CPU_NUMS%4)) / 4;
	unsigned line_width = 3;
	unsigned col_width = COLS/4;
	const unsigned cpu_length = 4;
	const unsigned TXT_LEN = 4;
	string l1 = "L1";
	string l2 = "L2";
	string l3 = "L3";
	for (int i=0;i<CPU_NUMS;++i) {
		blocks[i].line = (i%rows) * line_width;
		blocks[i].col = (i/rows)*col_width;
		blocks[i].cpu = new char[cpu_length];
		int len = sprintf(blocks[i].cpu, "%d", i);
		blocks[i].cpu[len] = '\0';

		mvprintw(blocks[i].line,blocks[i].col, "%s", blocks[i].cpu);

		blocks[i].l1x = blocks[i].line;
		blocks[i].l2x = blocks[i].line + 1;
		blocks[i].l3x = blocks[i].line + 2;

		blocks[i].ly = blocks[i].col + cpu_length + 2;

		blocks[i].ly_start = blocks[i].ly + LX_LENGTH + 2;
		blocks[i].ly_end = blocks[i].col + col_width -2 - TXT_LEN;

		blocks[i].txtStart = blocks[i].ly_end;
		blocks[i].txtEnd = blocks[i].txtStart + TXT_LEN;

		mvprintw(blocks[i].l1x,blocks[i].ly, "%s", l1.c_str());
		mvprintw(blocks[i].l2x,blocks[i].ly, "%s", l2.c_str());
		mvprintw(blocks[i].l3x,blocks[i].ly, "%s", l3.c_str());
	}

	return 0;
}

static unsigned get_line(struct view_block *block, MicroArch arch)
{
	switch (arch)
	{
	case MicroArch::L1:
		return block->l1x;
	case MicroArch::L2:
		return block->l2x;
	case MicroArch::L3:
		return block->l3x;
	default:
		return -1;
	}
}

static unsigned get_col(struct view_block *block, MicroArch arch)
{
	switch (arch)
	{
	case MicroArch::L1:
	case MicroArch::L2:
	case MicroArch::L3:
		return block->ly_start;
	default:
		return -1;
	}
}

int clear_cache_view()
{
	for (int i = 0; i < CPU_NUMS; ++i)
	{
		for (int j = blocks[i].ly_start; j < blocks[i].txtEnd; ++j)
		{
			mvprintw(blocks[i].l1x, j, " ");
			mvprintw(blocks[i].l2x, j, " ");
			mvprintw(blocks[i].l3x, j, " ");
		}
	}
	return 0;
}

string MetricToTxt(const unsigned long metric)
{
	if (metric/1e3 < 1) {
		return to_string(metric);
	} else if (metric/1e6 < 1) {
		return to_string(metric/1e3).substr(0,3) + "K";
	} else if (metric/1e9 < 1) {
		return to_string(metric/1e6).substr(0,3) + "M";
	} else if (metric/1e12 < 1) {
		return to_string(metric/1e9).substr(0,3) + "G";
	} else {
		return "";
	}
}

void ConvertToViewData(const map<Metric, unsigned long> &collData,
		const Metric ref,
		const Metric miss,
		const MicroArch arch,
		map<MicroArch, ViewData> &renderData)
{
	auto lRef = collData.at(ref);
	auto lMiss = collData.at(miss);
	if (lRef > lMiss) {
		renderData[arch].hit = lRef - lMiss;
	} else {
		renderData[arch].hit = 0;
	}
	renderData[arch].miss = lMiss;
}

int render(const std::map<int, cache_stat> &cacheStat)
{
	clear_cache_view();
	float max = 0;
	for (auto &stat : cacheStat)
	{
		for (auto &metric : stat.second.metrics)
		{
			max += metric.second;
		}
	}

	if (verbose) {
		log << "max: " << max << "\n";
	}

	for (auto &stat : cacheStat)
	{
		auto &cpu = stat.first;
		auto &collData = stat.second.metrics;
		map<MicroArch, ViewData> renderData;
		ConvertToViewData(collData, Metric::L1_REF, Metric::L1_MISS, MicroArch::L1, renderData);
		ConvertToViewData(collData, Metric::L2_REF, Metric::L2_MISS, MicroArch::L2, renderData);
		ConvertToViewData(collData, Metric::L3_REF, Metric::L3_MISS, MicroArch::L3, renderData);
		for (auto &metric : renderData)
		{
			unsigned hitWidth = metric.second.hit / max * (blocks[cpu].ly_end - blocks[cpu].ly_start + 1);
			unsigned line = get_line(&blocks[cpu], metric.first);
			if (line == -1)
				continue;
			unsigned col = get_col(&blocks[cpu], metric.first);
			if (col == -1)
				continue;

			attron(COLOR_PAIR(GREEN));
			for (int k = 0; k < hitWidth; ++k)
			{
				mvprintw(line, col + k, " ");
			}
			attroff(COLOR_PAIR(GREEN));

			unsigned missWidth = metric.second.miss / max * (blocks[cpu].ly_end - blocks[cpu].ly_start + 1);
			attron(COLOR_PAIR(RED));
			for (int k = hitWidth; k < hitWidth + missWidth; ++k)
			{
				mvprintw(line, col + k, " ");
			}
			attroff(COLOR_PAIR(RED));

			auto txt = MetricToTxt(metric.second.hit + metric.second.miss);
			if (txt != "" && txt != "0") {
				mvprintw(line, blocks[cpu].txtStart, "%s", txt.c_str());
				if (verbose) {
				log << "metric:" << metric.second.hit + metric.second.miss << 
					" txt: " << txt << "\n";
				}
			}
			if (verbose) {
				log << "cpu " << cpu << " hitw " << hitWidth << " missw " << missWidth
					<< " line " << line << " col " << col << "\n";
			}
		}
	}

	return 0;
}

int check_input()
{
	int ch = getch();
	if (ch == 'q') {
		return -1;
	}
	return 0;
}

int end_view()
{
	endwin();
	return 0;
}

void EnableVerbose()
{
	verbose = true;
	log.open(logpath, ofstream::out | ofstream::app);
	if (!log.is_open()) {
		verbose = false;
	}
}
